﻿#region License

// Another free, open solution by folks who get it.
// Copyright © 2011 Business in Unison, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Threading;

using Patterns.Collections;
using Patterns.SafeCode;

namespace Patterns.Cache
{
	/// <summary>
	/// Defines an in-memory cache suitable for use outside of a web context.
	/// </summary>
	/// <remarks>
	/// Always code against the interface (<see cref="ICache"/>).  This class uses
	/// explicit interface implementation to avoid collisions with its
	/// <see cref="Dictionary{K,V}"/> base class.
	/// </remarks>
	public class MemoryCache : Dictionary<string,CachedItem>, ICache
	{
		private readonly ICache _me;
		private readonly CacheEventMessenger _messenger = new CacheEventMessenger();
		private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
		private static readonly ICache _current = new MemoryCache();

		/// <summary>
		/// Initializes a new instance of the <see cref="MemoryCache"/> class.
		/// </summary>
		public MemoryCache()
		{
			_me = this;
		}

		/// <summary>
		/// Gets the current cache.
		/// </summary>
		/// <value>The current.</value>
		public static ICache Current
		{
			get { return _current; }
		}

		object ICache.this[string key]
		{
			get { return Try.Get(() => _me.Get<object>(key)); }
			set { Try.Do(() => _me.Set(key, () => value)); }
		}

		T ICache.Get<T>(string key, Func<T> factory, DateTime? absoluteExpiration, TimeSpan? slidingExpiration)
		{
			_lock.TryEnterUpgradeableReadLock(-1);
			CachedItem cachedItem = Try.Get(() => this.GetValue(key));
			T value = cachedItem != null ? cachedItem.Value as T : null;
			if (value == null && factory != null)
				_me.Set(key, factory, absoluteExpiration, slidingExpiration);
			else if (value != null && cachedItem.HasExpired(true))
			{
				_me.Remove(key);
				return null;
			}
			_lock.ExitUpgradeableReadLock();
			return value;
		}

		ICache ICache.Set<T>(string key, Func<T> factory, DateTime? absoluteExpiration, TimeSpan? slidingExpiration)
		{
			_lock.TryEnterWriteLock(-1);
			T value = factory();
			Try.Do(() => Add(key, new CachedItem
			{
				ExpirationDate = absoluteExpiration,
				SlidingExpiration = slidingExpiration,
				StartDate = DateTime.Now,
				Value = value
			}));
			_messenger.SendMessages(key, value, CacheEventType.Add, true);
			_lock.ExitWriteLock();
			return this;
		}

		ICache ICache.Remove(string key)
		{
			_lock.TryEnterWriteLock(-1);
			CachedItem cachedItem = Try.Get(() => this.GetValue(key));
			Remove(key);
			_messenger.SendMessages(key, cachedItem.Value, CacheEventType.Remove, true);
			_lock.ExitWriteLock();
			return this;
		}

		ICache ICache.OnUpdate(string key, Action<CacheEvent> action)
		{
			_messenger.AddRecipient(key, action);
			return this;
		}
	}
}